package org.hackystat.sensor.manual.devtime;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.sensorshell.SensorShell;
import org.hackystat.sensorshell.SensorShellException;
import org.hackystat.sensorshell.SensorShellProperties;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Manager to process report entries.
 * @author Shaoxuan
 *
 */
public class SensorManager implements Serializable {
  /** Support Serialization.*/
  private static final long serialVersionUID = -3633070207932520487L;
  
  /** Tool name. */
  protected static final String SELF_REPORT_PROPERTY = "SelfReport";
  /** The sensor data type the sensor collect. */
  protected static final String SDT = "DevTime";
  /** Whether or not to print out additional messages during execution. */
  protected boolean verbose = true;
  /** The sensor shell instance used by this sensor. */
  protected transient SensorShell sensorShell;
  /** Sensor properties to be used with the sensor. */
  protected transient SensorShellProperties sensorProps;
  
  private ReportEntry report = new ReportEntry();
  private HackyReporter hackyReporter;
  
  /**
   * Initialize the sensorshell with the given properties.
   * @param hackyReporter the associated reporter.
   * @param props the properties.
   */
  public SensorManager(SensorShellProperties props, HackyReporter hackyReporter) {
    this.hackyReporter = hackyReporter;
    this.sensorProps = props;
    setupSensorShell();
  }
  
  /**
   * Check if the account is authenticated.
   * @return true if the account is authenticated.
   */
  public boolean authenticate() {
    String host = sensorShell.getProperties().getSensorBaseHost();
    String user = sensorShell.getProperties().getSensorBaseUser();
    String password = sensorShell.getProperties().getSensorBasePassword();
    SensorBaseClient client = new SensorBaseClient(host, user, password);
    try {
      client.authenticate();
      return true;
    }
    catch (SensorBaseClientException e) {
      //e.printStackTrace();
      return false;
    }
  }
  
  /**
   * Process the ReportEntry to SensorShell.
   * @param report the ReportEntry.
   * @throws SensorShellException error when processing to SensorShell.
   */
  protected void processReportRecord(ReportEntry report) throws SensorShellException {
    if (!authenticate()) {
      throw new SensorShellException("Unable to authenticate user.");
    }
    XMLGregorianCalendar runTime = Tstamp.makeTimestamp();
    
    XMLGregorianCalendar timeStamp = Tstamp.makeTimestamp(report.getStartTime().getTime());
    XMLGregorianCalendar endTimeStamp = Tstamp.makeTimestamp(report.getEndTime().getTime());
    if (this.verbose) {
      System.out.printf("Sending Report entry: Timestamp: %s Resource: %s User: %s", 
          timeStamp.toString(), report.getResource(), 
          sensorShell.getProperties().getSensorBaseUser());
    }
    while (timeStamp.compare(endTimeStamp) == DatatypeConstants.LESSER) {
      Map<String, String> pMap = new HashMap<String, String>();
      String timestampString = timeStamp.toString();
      pMap.put("SensorDataType", SDT);
      pMap.put("Resource", report.getResource());
      pMap.put("Tool", report.getTool());
      pMap.put("Timestamp", timestampString);
      pMap.put("Runtime", runTime.toString());
      pMap.put(SELF_REPORT_PROPERTY, "true");
      sensorShell.add(pMap);   
      if (this.verbose) {
        System.out.print(".");
      }
      timeStamp = Tstamp.incrementMinutes(timeStamp, 5);
    }      
    sensorShell.send();
    if (this.verbose) {
      System.out.println(" Done.");
    }
  }
  
  /**
   * Instantiates the internal sensorshell using either the data in the UserMap or the data in
   * sensorshell.properties. Does not instantiate the sensorshell if it already has been done in the
   * testing constructor.
   * 
   * DO NOT call this method in your constructor. Instead, call it at the beginning of your
   * execute() method. The reason is that the toolAccount property which controls where the
   * SensorShell data is obtained is not set until Ant finishes constructor setup.
   */
  private void setupSensorShell() {
    Properties preferMultiShell = new Properties();

    // Instantiate the sensorshell using sensorshell.properties, unless we've already created one
    // using the test constructor.
    if (this.sensorProps == null) {
      verboseInfo("Creating a SensorShell using sensorshell.properties data...");
      // use the sensor.properties file
      try {
        this.sensorProps = new SensorShellProperties(preferMultiShell, false);
      }
      catch (SensorShellException e) {
        System.out.println("Problem creating the SensorShell: " + e.getMessage());
      }
    }
    if (this.sensorShell == null) {
      this.sensorShell = new SensorShell(this.sensorProps, false);
    }
    verboseInfo(this.sensorShell.getProperties().toString());
    verboseInfo("Maximum Java heap size is: " + Runtime.getRuntime().maxMemory());
    
  }

  /**
   * Output a verbose message if verbose is enabled.
   * 
   * @param msg The message to print if verbose mode is enabled.
   */
  protected final void verboseInfo(String msg) {
    if (this.verbose) {
      System.out.println(msg);
    }
  }

  /**
   * Sends any accumulated data in the SensorShell to the server and quits the shell.
   */
  protected void sendAndQuit() {
    try {
      this.sensorShell.quit();
      if (this.verbose) {
        System.out.println("Application closed.");
      }
    }
    catch (SensorShellException e) {
      System.out.println("Problem during quit() of SensorShell:" + e.getMessage());
    }
  }
  
  /**
   * @param message the message to put in status panel.
   */
  public void setStatusMessage(String message) {
    hackyReporter.setStatusMessage(message);
  }

  /**
   * @param report the report to set
   */
  public void setReport(ReportEntry report) {
    this.report = report;
  }

  /**
   * @return the report
   */
  public ReportEntry getReport() {
    return report;
  }
}
